using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine.Pool;
using Object = UnityEngine.Object;

namespace AGame.TabOrm.TabOrmAttributeExpand
{
    /// <summary>
    /// 资源转换 这个谨慎使用
    /// 因为这个标记之后，会在实例化的时候把资源加载进来
    /// 如果表很长，那么会直接把表上所有资源马上加载进来，会卡
    /// 所以推荐使用LazyAsset 来加载资源
    /// </summary>
    public class AssetDatabaseTabField : TabField
    {
        public string LoadPath = "Assets/";
        public string Suffix = string.Empty;

        public AssetDatabaseTabField(string name = null) : base(name)
        {
        }

        public override Func<string, object> GetStrToObjFunc(Type tabType, Type targetType)
        {
            if (targetType.IsArray)
            {
                var elementType = targetType.GetElementType();
                if (elementType == null)
                {
                    return null;
                }

                if (elementType.IsGenericType)
                {
                    var typeDefinition = elementType.GetGenericTypeDefinition();
                    if (typeDefinition == typeof(LazyAsset<>))
                    {
                        return s => GetAssetArray(s, elementType, CreateLazyAsset);
                    }
                }
                else if (targetType.IsSubclassOf(typeof(Object)))
                {
                    return s => GetAssetArray(s, elementType, CreateAssetDatabase);
                }
            }
            else if (targetType.IsGenericType)
            {
                var typeDefinition = targetType.GetGenericTypeDefinition();
                if (typeDefinition == typeof(LazyAsset<>))
                {
                    return s => CreateLazyAsset(targetType, s);
                }
                if (typeDefinition != typeof(List<>)) return null;
                var arguments = typeDefinition.GetGenericArguments();
                var argument = arguments[0];
                if (argument.IsGenericType)
                {
                    typeDefinition = argument.GetGenericTypeDefinition();
                    if (typeDefinition == typeof(LazyAsset<>))
                    {
                        return s => Activator.CreateInstance(targetType, GetAssetArray(s, argument, CreateLazyAsset));
                    }
                }
                else if (argument.IsSubclassOf(typeof(Object)))
                {
                    return s => Activator.CreateInstance(targetType, GetAssetArray(s, argument, CreateAssetDatabase));
                }
            }
            else if (targetType.IsSubclassOf(typeof(Object)))
            {
                return str => CreateAssetDatabase(targetType, str);
            }

            return null;
        }

        private static Array GetAssetArray(string s, Type elementType, Func<Type, string, object> assetFunc)
        {
            if (string.IsNullOrWhiteSpace(s))
            {
                return Array.CreateInstance(elementType, 0);
            }
            var objArray = s.Split('|')
                .Select(str => assetFunc(elementType, s))
                .ToArray();
            var instance = Array.CreateInstance(elementType, objArray.Length);
            objArray.CopyTo(instance, 0);
            return instance;
        }

        private object CreateLazyAsset(Type targetType, string str)
        {
            return Activator.CreateInstance(targetType, LoadPath, Suffix, str);
        }

        private object CreateAssetDatabase(Type targetType, string str)
        {
            return AssetDatabase.LoadAssetAtPath(Path.Combine(LoadPath, str) + Suffix, targetType);
        }

        private string GetAssetDatabasePath(object assetData)
        {
            var assetPath = AssetDatabase.GetAssetPath((Object) assetData);
            if (assetPath.Length >= LoadPath.Length + Suffix.Length)
            {
                assetPath = assetPath.Substring(LoadPath.Length,
                    assetPath.Length - (LoadPath.Length + Suffix.Length));
            }

            return assetPath;
        }

        private string GetLazyAssetPath(object lazyAsset, PropertyInfo propertyInfo)
        {
            return propertyInfo.GetValue(lazyAsset) as string;
        }

        public override Func<object, string> GetObjToStrFunc(Type tabType, Type targetType)
        {
            if (targetType.IsArray)
            {
                var elementType = targetType.GetElementType();
                if (elementType == null) return null;
                if (elementType.IsGenericType)
                {
                    var typeDefinition = elementType.GetGenericTypeDefinition();
                    if (typeDefinition != typeof(LazyAsset<>)) return null;
                    var propertyInfo = elementType.GetProperty("AssetFullName");
                    if (propertyInfo == null) return null;
                    return obj =>
                    {
                        if (!(obj is IEnumerable iEnumerable))
                        {
                            return string.Empty;
                        }

                        var list = ListPool<string>.Get();
                        list.AddRange(from object o in iEnumerable select GetLazyAssetPath(o, propertyInfo));
                        var s = string.Join("|", list);
                        ListPool<string>.Release(list);
                        return s;
                    };
                }

                if (targetType.IsSubclassOf(typeof(Object)))
                {
                    return obj =>
                    {
                        if (!(obj is IEnumerable iEnumerable))
                        {
                            return string.Empty;
                        }

                        var list = ListPool<string>.Get();
                        list.AddRange(from object o in iEnumerable select GetAssetDatabasePath(o));
                        var s = string.Join("|", list);
                        ListPool<string>.Release(list);
                        return s;
                    };
                }
            }
            else if (targetType.IsGenericType)
            {
                var typeDefinition = targetType.GetGenericTypeDefinition();
                if (typeDefinition == typeof(LazyAsset<>))
                {
                    var propertyInfo = targetType.GetProperty("AssetFullName");
                    if (propertyInfo == null) return null;
                    return obj => GetLazyAssetPath(obj, propertyInfo);
                }

                if (typeDefinition != typeof(List<>)) return null;
                var arguments = typeDefinition.GetGenericArguments();
                var argument = arguments[0];
                if (argument.IsGenericType)
                {
                    typeDefinition = argument.GetGenericTypeDefinition();
                    if (typeDefinition != typeof(LazyAsset<>)) return null;
                    var propertyInfo = argument.GetProperty("AssetFullName");
                    if (propertyInfo == null) return null;
                    return obj =>
                    {
                        if (!(obj is IEnumerable iEnumerable))
                        {
                            return string.Empty;
                        }

                        var list = ListPool<string>.Get();
                        list.AddRange(from object o in iEnumerable select GetLazyAssetPath(o, propertyInfo));
                        var s = string.Join("|", list);
                        ListPool<string>.Release(list);
                        return s;
                    };
                }

                if (argument.IsSubclassOf(typeof(Object)))
                {
                    return obj =>
                    {
                        if (!(obj is IEnumerable iEnumerable))
                        {
                            return string.Empty;
                        }

                        var list = ListPool<string>.Get();
                        list.AddRange(from object o in iEnumerable select GetAssetDatabasePath(o));
                        var s = string.Join("|", list);
                        ListPool<string>.Release(list);
                        return s;
                    };
                }
            }
            else if (targetType.IsSubclassOf(typeof(Object)))
            {
                return GetAssetDatabasePath;
            }

            return null;
        }
    }

    /// <summary>
    /// 资源加载，用于将两字段引用资源的情况
    /// </summary>
    public class TwoFieldAsset<T> where T : UnityEngine.Object
    {
        private readonly string _prefixPath;
        private readonly string _suffix;
        private string _srcAssetPath;
        private string _srcAssetName;
        private T _assetObj;

        public TwoFieldAsset(string prefixPath, string suffix)
        {
            _prefixPath = prefixPath;
            _suffix = suffix;
        }

        public T Get(string assetPath, string assetName)
        {
            if (_srcAssetPath == assetPath && _srcAssetName == assetName)
            {
                return _assetObj;
            }

            if (string.IsNullOrEmpty(assetPath) || string.IsNullOrEmpty(assetName))
            {
                _assetObj = null;
                return null;
            }

            _srcAssetPath = assetPath;
            _srcAssetName = assetName;
            return _assetObj =
                AssetDatabase.LoadAssetAtPath<T>($"{Path.Combine(_prefixPath, assetPath, assetName)}{_suffix}");
        }

        public void Set(T assObj, ref string assetPath, ref string assetName)
        {
            if (_assetObj == assObj)
            {
                return;
            }

            _assetObj = assObj;
            if (assObj)
            {
                var assetFullPath = AssetDatabase.GetAssetPath(_assetObj);
                if (assetFullPath.StartsWith(_prefixPath))
                {
                    assetFullPath = assetFullPath.Remove(assetFullPath.Length - _suffix.Length)
                        .Remove(0, _prefixPath.Length);
                    var lastIndexOf = assetFullPath.LastIndexOf("/", StringComparison.Ordinal);
                    assetPath = assetFullPath.Substring(0, lastIndexOf);
                    assetName = assetFullPath.Substring(lastIndexOf + 1);
                }
                else
                {
                    _assetObj = null;
                    assetPath = null;
                    assetName = null;
                }
            }
            else
            {
                assetPath = null;
                assetName = null;
            }

            _srcAssetPath = assetPath;
            _srcAssetName = assetName;
        }
    }


    /// <summary>
    /// 懒资源加载，路径设置好之后不会立马加载资源，会在Get的时候触发资源加载
    /// </summary>
    public class LazyAsset<T> where T : Object
    {
        public const string DefaultLoadPath = "Assets/";
        private readonly string _prefixPath;
        private readonly string _suffix;
        private string _assetPath;
        private string _assetName;
        private T _assetObj;


        public static implicit operator T(LazyAsset<T> exists)
        {
            return exists.Get();
        }
        
        public static implicit operator string(LazyAsset<T> exists)
        {
            return exists.AssetFullName;
        }
        
        public LazyAsset()
        {
            _prefixPath = DefaultLoadPath;
            _suffix = "";
        }

        public LazyAsset(string prefixPath, string suffix = "")
        {
            _prefixPath = prefixPath;
            _suffix = suffix;
        }

        public LazyAsset(string prefixPath, string suffix, string assetFull)
        {
            _prefixPath = prefixPath;
            _suffix = suffix;
            AssetFullName = assetFull;
        }

        public LazyAsset(string prefixPath, string suffix, string assetPath, string assetName)
        {
            _prefixPath = prefixPath;
            _suffix = suffix;
            _assetPath = assetPath;
            _assetName = assetName;
        }

        public string AssetPath
        {
            get => _assetPath;
            set
            {
                if (value == _assetPath) return;
                _assetPath = value;
                _assetObj = null;
            }
        }

        public string AssetName
        {
            get => _assetName;
            set
            {
                if (value == _assetName) return;
                _assetName = value;
                _assetObj = null;
            }
        }

        public string AssetFullName
        {
            get
            {
                if (string.IsNullOrEmpty(_assetName))
                {
                    return string.Empty;
                }

                if (string.IsNullOrEmpty(_assetPath))
                {
                    return _assetName ?? string.Empty;
                }

                return _assetPath + '/' + _assetName;
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    return;
                }

                var lastIndexOf = value.LastIndexOf("/", StringComparison.Ordinal);
                if (lastIndexOf < 0)
                {
                    _assetName = value;
                    return;
                }

                _assetPath = value.Substring(0, lastIndexOf);
                _assetName = value.Substring(lastIndexOf + 1);
            }
        }

        public string AssetFullPath => _prefixPath + AssetFullName + _suffix;

        public T Get()
        {
            if (_assetObj)
            {
                return _assetObj;
            }

            if (string.IsNullOrEmpty(_assetName))
            {
                return null;
            }

            if (string.IsNullOrEmpty(_assetPath))
            {
                return _assetObj =
                    AssetDatabase.LoadAssetAtPath<T>($"{Path.Combine(_prefixPath, _assetName)}{_suffix}");
            }

            return _assetObj =
                AssetDatabase.LoadAssetAtPath<T>($"{Path.Combine(_prefixPath, _assetPath, _assetName)}{_suffix}");
        }

        public void Set(T assObj)
        {
            if (_assetObj == assObj)
            {
                return;
            }

            _assetObj = assObj;
            if (!assObj)
            {
                _assetPath = null;
                _assetName = null;
            }

            var assetFullPath = AssetDatabase.GetAssetPath(_assetObj);
            if (!assetFullPath.StartsWith(_prefixPath))
            {
                _assetObj = null;
                _assetPath = null;
                _assetName = null;
                return;
            }

            if (_suffix.Length > 0)
            {
                assetFullPath = assetFullPath.Remove(assetFullPath.Length - _suffix.Length);
            }

            assetFullPath = assetFullPath.Remove(0, _prefixPath.Length);
            AssetFullName = assetFullPath;
        }

        public void Set(string fullPath)
        {
            if (!fullPath.StartsWith(_prefixPath))
            {
                _assetPath = null;
                _assetName = null;
                _assetObj = null;
                return;
            }

            fullPath = fullPath.Remove(fullPath.Length - _suffix.Length)
                .Remove(0, _prefixPath.Length);
            var lastIndexOf = fullPath.LastIndexOf("/", StringComparison.Ordinal);
            var assetPath = fullPath.Substring(0, lastIndexOf);
            var assetName = fullPath.Substring(lastIndexOf + 1);
            if (assetPath == _assetPath && assetName == _assetName)
            {
                return;
            }

            _assetPath = assetPath;
            _assetName = assetName;
            _assetObj = null;
        }
    }
}